﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace uMVP.AutofacExtends
{
    public static class ContainerBuilderExtends
    {
        public const string GameObjectParameterName = "GameObject";

        public static (IRegistrationBuilder<TViewClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>,
            IRegistrationBuilder<TPresenterClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>)
            RegisterMvp<TViewInterface, TViewClass, TPresenterInterface, TPresenterClass>(this ContainerBuilder builder)
            where TViewInterface : class
            where TViewClass : class, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            var presenterBuilder = builder.RegisterPresenter<TViewInterface, TPresenterInterface, TPresenterClass>();
            var viewBuilder = RegisterView<TViewInterface, TViewClass, TPresenterInterface>(builder);
            return (viewBuilder, presenterBuilder);
        }
        
        public static IRegistrationBuilder<TViewClass, ConcreteReflectionActivatorData, SingleRegistrationStyle> 
            RegisterMvp<TViewInterface, TViewClass, TPresenterInterface>(this ContainerBuilder builder, TPresenterInterface presenter)
            where TViewInterface : class
            where TViewClass : class, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            builder.RegisterInstance(presenter).As<TPresenterInterface>();
            var prefabPathAttribute = GetPrefabPathAttribute(typeof(TViewClass));
            var registrationBuilder = builder.RegisterType<TViewClass>()
                .OnActivated(e =>
                {
                    InitializePrefabInstance(e.Instance);
                    InitializeViewAndPresenterAsync(presenter, e.Instance).Forget();
                })
                .As<TViewInterface>();

            if (prefabPathAttribute == null || string.IsNullOrEmpty(prefabPathAttribute.ViewName))
                return registrationBuilder;
            
            return registrationBuilder.Named<IView>(prefabPathAttribute.ViewName);
        }

        private static async UniTask InitializeViewAndPresenterAsync<TPresenterInterface>(TPresenterInterface presenter,
            IView<TPresenterInterface> view) where TPresenterInterface : class
        {
            await view.InitializeAsync(presenter);
            await ((IPresenter)presenter).InitializeAsync();
        }

        public static (IRegistrationBuilder<TViewClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>,
            IRegistrationBuilder<TPresenterInterface, SimpleActivatorData, SingleRegistrationStyle>)
            RegisterMvp<TViewInterface, TViewClass, TPresenterInterface>(this ContainerBuilder builder,
                Func<IComponentContext, IEnumerable<Parameter>, TViewInterface, TPresenterInterface> presenterFactory)
            where TViewInterface : class
            where TViewClass : class, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            var registerBuilderPresenter = builder.RegisterPresenter(presenterFactory);
            var viewBuilder = RegisterView<TViewInterface, TViewClass, TPresenterInterface>(builder);
            return (viewBuilder, registerBuilderPresenter);
        }

        public static (IRegistrationBuilder<TViewClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>,
            IRegistrationBuilder<TPresenterClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>)
            RegisterMvp<TViewInterface, TViewClass, TPresenterInterface, TPresenterClass, TPresenterFactory>
            (this ContainerBuilder builder)
            where TViewInterface : class
            where TViewClass : class, IView<TPresenterInterface, TPresenterFactory>
            where TPresenterInterface : class
            where TPresenterClass : class
            where TPresenterFactory : Delegate
        {
            var registerBuilderPresenter = builder.RegisterPresenter<TPresenterInterface, TPresenterClass>();
            var registerBuilderView = builder.RegisterType<TViewClass>()
                .OnActivated(e =>
                {
                    InitializePrefabInstance(e.Instance);
                    var factory = e.Context.Resolve<TPresenterFactory>();
                    e.Instance.InitializeAsync(factory).Forget();
                })
                .AsSelf()
                .As<TViewInterface>();
            return (registerBuilderView, registerBuilderPresenter);
        }

        public static (IRegistrationBuilder<TViewClass, SimpleActivatorData, SingleRegistrationStyle>,
            IRegistrationBuilder<TPresenterClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>)
            RegisterMvp<TViewInterface, TViewClass, TPresenterInterface, TPresenterClass>(
                this ContainerBuilder builder, string viewPrefabPath)
            where TViewInterface : class
            where TViewClass : Component, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            var registerBuilderPresenter =
                builder.RegisterPresenter<TViewInterface, TPresenterInterface, TPresenterClass>();
            var registerBuilderView = builder.Register((context, p) =>
                {
                    var prefab = Resources.Load<GameObject>(viewPrefabPath);
                    var go = UnityEngine.Object.Instantiate(prefab);
                    return go.AddComponent<TViewClass>();
                })
                .OnActivating(CreatePresenter<TViewInterface, TViewClass, TPresenterInterface>)
                .AsSelf()
                .As<TViewInterface>();
            return (registerBuilderView, registerBuilderPresenter);
        }

        public static void InitializePrefabInstance(object view)
        {
            MemberInfo info = view.GetType();
            var prefabPathAttribute = GetPrefabPathAttribute(info);
            string prefabPath = null;
            string parentPath = null;
            if (prefabPathAttribute != null)
            {
                prefabPath = prefabPathAttribute.PrefabPath;
                parentPath = prefabPathAttribute.ParentPath;
            }

            ((IViewResourceInitializer) view).InitializePrefabInstance(prefabPath, parentPath);
        }

        public static PrefabPathAttribute GetPrefabPathAttribute(MemberInfo memberInfo)
        {
            var attributes = memberInfo.GetCustomAttributes(typeof(PrefabPathAttribute), true);
            if (attributes.Length != 0)
            {
                return (PrefabPathAttribute) attributes[0];
            }

            return null;
        }

        public static IRegistrationBuilder<TViewClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>
            RegisterView<TViewInterface, TViewClass>(this ContainerBuilder builder)
            where TViewInterface : class
            where TViewClass : IView
        {
            return builder.RegisterType<TViewClass>()
                .OnActivated(eventArgs =>
                {
                    InitializePrefabInstance(eventArgs.Instance);
                    eventArgs.Instance.InitializeAsync();
                })
                .As<TViewInterface>()
                .AsSelf();
        }

        private static IRegistrationBuilder<TViewClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>
            RegisterView<TViewInterface, TViewClass, TPresenterInterface>(ContainerBuilder builder)
            where TViewInterface : class
            where TViewClass : class, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            var prefabPathAttribute = GetPrefabPathAttribute(typeof(TViewClass));
            var registrationBuilder = builder.RegisterType<TViewClass>()
                .OnActivating(e =>
                {
                    InitializePrefabInstance(e.Instance);
                    CreatePresenter<TViewInterface, TViewClass, TPresenterInterface>(e);
                })
                .As<TViewInterface>();

            if (prefabPathAttribute == null || string.IsNullOrEmpty(prefabPathAttribute.ViewName))
                return registrationBuilder;
            
            return registrationBuilder.Named<IView>(prefabPathAttribute.ViewName);
        }

        public static IRegistrationBuilder<TPresenterClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>
            RegisterPresenter<TPresenterInterface, TPresenterClass>(this ContainerBuilder builder)
        {
            return builder.RegisterType<TPresenterClass>()
                .AsSelf()
                .As<TPresenterInterface>();
        }

        private static void CreatePresenter<TViewInterface, TViewClass, TPresenterInterface>(
            IActivatingEventArgs<TViewClass> e)
            where TViewInterface : class
            where TViewClass : class, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            var factory = e.Context.Resolve<Func<TViewInterface, TPresenterInterface>>();
            var instance = e.Instance as TViewInterface;

            var presenter = factory(instance);
            InitializeViewAndPresenter(e, presenter).Forget();
        }

        private static async UniTask
            InitializeViewAndPresenter<TViewClass, TPresenterInterface>(IActivatingEventArgs<TViewClass> e,
                TPresenterInterface presenter) where TViewClass : class, IView<TPresenterInterface>
            where TPresenterInterface : class
        {
            await e.Instance.InitializeAsync(presenter);
            await ((IPresenter)presenter).InitializeAsync();
        }

        private static IRegistrationBuilder<TPresenterInterface, SimpleActivatorData, SingleRegistrationStyle>
            RegisterPresenter<TViewInterface, TPresenterInterface>(this ContainerBuilder builder,
                Func<IComponentContext, IEnumerable<Parameter>, TViewInterface, TPresenterInterface> presenterFactory)
        {
            return builder.Register((context, p) =>
                {
                    var parameter = (TypedParameter) p.First();
                    return presenterFactory(context, p, (TViewInterface) parameter.Value);
                })
                .AsSelf()
                .As<TPresenterInterface>();
        }

        private static IRegistrationBuilder<TPresenterClass, ConcreteReflectionActivatorData, SingleRegistrationStyle>
            RegisterPresenter<TViewInterface, TPresenterInterface, TPresenterClass>(this ContainerBuilder builder)
        {
            object view = null;
            return builder.RegisterType<TPresenterClass>()
                .AsSelf()
                .As<TPresenterInterface>()
                .OnPreparing(preparingEventArgs =>
                {
                    foreach (var p in preparingEventArgs.Parameters)
                    {
                        var typeParam = (TypedParameter) p;
                        if (IsViewInterface<TViewInterface>(typeParam.Type))
                        {
                            view = typeParam.Value;
                            break;
                        }
                    }
                })
                .WithParameter(
                    new ResolvedParameter(
                        (parameterInfo, ctx) => IsViewInterface<TViewInterface>(parameterInfo.ParameterType),
                        (parameterInfo, ctx) => view));
        }

        private static bool IsViewInterface<TViewInterface>(Type typeParam)
        {
            return typeParam == typeof(TViewInterface) || typeParam == typeof(IViewDestroy);
        }

        private static TViewClass CreateViewComponent<TViewClass>(IEnumerable<Parameter> p)
            where TViewClass : Component
        {
            var go = GetGameObject(p);
            if (go == null)
                throw new InvalidOperationException("没得到GameObject对象");

            var viewComponent = go.AddComponent<TViewClass>();
            return viewComponent;
        }

        private static GameObject GetGameObject(IEnumerable<Parameter> parameters)
        {
            return parameters.Named<GameObject>(GameObjectParameterName);
        }
    }
}